Tailscale 組み込みのリバースプロキシでVPN向けWebアプリをHTTPS対応する
2023/03/25更新 Tailscaleエージェントv1.38.1でserve、funnelサブコマンドが変更されたのを反映
ども、大瀧です。
TailscaleのVPNエージェントには、様々な組み込み機能があります。本ブログではそのうちのひとつであるリバースプロキシ機能を利用し、TailscaleのVPN経由でWebアプリケーションをホストする様子をご紹介します。
TailscaleエージェントのWebサーバー機能
TailscaleエージェントのWebサーバー機能は、一般的なWebサーバーと同様にドキュメントルートを指定するほかシンプルな固定テキストとリバースプロキシを指定できます。詳しくは以下のドキュメントや設定コマンドのヘルプ tailscale serve --help
を参照してください。
TailscaleデバイスでWebアプリケーションがポートをListenし他のTailscaleデバイスからの接続を受け付けることももちろん出来るのですが、リバースプロキシを経由することでTLS終端を手軽に利用できるメリットがあります。Tailscale HTTPSというTailscaleデバイスに割り当てられるVPN内のFQDN [デバイス名].tailnet-XXXX.ts.net
のTLS証明書を提供する機能があり、リバースプロキシ経由の通信に自動で適用されます。
設定と動作確認
設定方法は非常にシンプルです。TailscaleのWeb管理画面の[DNS]でTailscale HTTPSが有効になっていることを確認します。
設定コマンドでは、以下の形式で設定します(バージョンv1.38.1で変わったので併記します)。
tailscale serve https [パス] http://127.0.0.1:[ポート番号]
tailscale serve [パス] proxy [ポート番号]
今回は以下の環境で動作確認しました。
- プラットフォーム: Amazon EC2 東京リージョン
- OS: Amazon Linux 2
- アーキテクチャ: Arm64
- Tailscaleバージョン: 1.36.2
まずは、プロキシ先のWebアプリケーションとしてこちらの記事のDockerコンテナを使い回します。
$ sudo amazon-linux-extras install docker $ sudo service docker start Redirecting to /bin/systemctl start docker.service $ sudo docker run -d -p 6060:80 traefik/whoami Unable to find image 'traefik/whoami:latest' locally latest: Pulling from traefik/whoami 029cd1bf7e7c: Pull complete e73b694ead4f: Pull complete cd132d421fcc: Pull complete Digest: sha256:24829edb0dbaea072dabd7d902769168542403a8c78a6f743676af431166d7f0 Status: Downloaded newer image for traefik/whoami:latest fdac84837295ae8d7543be33370f9bd05db68cdd4bc3257a6d230e8978f65af6 $
6060番ポートのルートパス(/
)に対してレスポンスとしてリクエスト情報を返す、シンプルなWebアプリケーションです。
$ curl localhost:6060 Hostname: fdac84837295 IP: 127.0.0.1 IP: 172.17.0.2 RemoteAddr: 172.17.0.1:36454 GET / HTTP/1.1 Host: localhost:6060 User-Agent: curl/7.88.1 Accept: */* $
では、この6060番ポートにリバースプロキシを設定します。
$ sudo tailscale serve https / http://127.0.0.1:6060 $ tailscale serve status https://ip-XX-XX-XX-XX.tailnet-XXXX.ts.net (tailnet only) |-- / proxy http://127.0.0.1:6060 $
これでOKです。手元のTailscaleデバイスからTailscaleのFQDNにHTTPSでアクセスしてみると...
% curl https://ip-XX-XX-XX-XX.tailnet-XXXX.ts.net Hostname: fdac84837295 IP: 127.0.0.1 IP: 172.17.0.2 RemoteAddr: 172.17.0.1:50648 GET / HTTP/1.1 Host: ip-XX-XX-XX-XX.tailnet-XXXX.ts.net User-Agent: curl/7.86.0 Accept: */* Accept-Encoding: gzip X-Forwarded-For: 100.113.200.88 %
レスポンスが返ってきました! X-Forwarded-Forヘッダから、Tailscale VPN(100.64.0.0/10
のShared Address Space)経由ということがわかりますね。
ちなみにcURLのオプションを調整してTLS証明書の様子を確認してみると...
% curl -v -I https://ip-XX-XX-XX-XX.tailnet-XXXX.ts.net * Trying 100.108.243.21:443... * Connected to ip-XX-XX-XX-XX.tailnet-XXXX.ts.net (100.108.243.21) port 443 (#0) * ALPN: offers h2 * ALPN: offers http/1.1 * TLSv1.3 (OUT), TLS handshake, Client hello (1): * TLSv1.3 (IN), TLS handshake, Server hello (2): * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): * TLSv1.3 (IN), TLS handshake, Certificate (11): * TLSv1.3 (IN), TLS handshake, CERT verify (15): * TLSv1.3 (IN), TLS handshake, Finished (20): * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): * TLSv1.3 (OUT), TLS handshake, Finished (20): * SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 * ALPN: server accepted h2 * Server certificate: * subject: CN=ip-XX-XX-XX-XX.tailnet-XXXX.ts.net * start date: Mar 11 21:32:30 2023 GMT * expire date: Jun 9 21:32:29 2023 GMT * subjectAltName: host "ip-XX-XX-XX-XX.tailnet-XXXX.ts.net" matched cert's "ip-172-31-47-65.tailnet-XXXX.ts.net" * issuer: C=US; O=Let's Encrypt; CN=R3 * SSL certificate verify ok. * Using HTTP2, server supports multiplexing * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 * h2h3 [:method: HEAD] * h2h3 [:path: /] * h2h3 [:scheme: https] * h2h3 [:authority: ip-XX-XX-XX-XX.tailnet-XXXX.ts.net] * h2h3 [user-agent: curl/7.86.0] * h2h3 [accept: */*] * Using Stream ID: 1 (easy handle 0x15a813200) > HEAD / HTTP/2 > Host: ip-XX-XX-XX-XX.tailnet-XXXX.ts.net > user-agent: curl/7.86.0 > accept: */* > * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): * Connection state changed (MAX_CONCURRENT_STREAMS == 250)! < HTTP/2 200 HTTP/2 200 < content-type: text/plain; charset=utf-8 content-type: text/plain; charset=utf-8 < date: Sat, 11 Mar 2023 22:33:45 GMT date: Sat, 11 Mar 2023 22:33:45 GMT < content-length: 214 content-length: 214 < * Connection #0 to host ip-XX-XX-XX-XX.tailnet-XXXX.ts.net left intact
IssuerからLet's EncryptのTLS証明書ということがわかりますね。Let's EncryptのTLS証明書を利用するためにはACMEと呼ばれる証明書発行のための手続きをセットアップする必要がありますが、Tailscale HTTPSはその手続きをマネージドで任せられるわけですね。便利!
ちなみに、ローカルホスト以外にプロキシを設定しようとすると、非サポートということでエラーになります。
$ sudo tailscale serve https / http://172.31.1.1:6060 only localhost or 127.0.0.1 proxies are currently supported $
Subnet Routerの代わりになったら便利かなと思っていたのですが、現時点では無理そうです。TraefikのTailscale HTTPS連携で代替しましょう。
まとめ
Taiscaleエージェントのリバースプロキシ機能を利用し、WebアプリケーションをHTTPSでホストする様子をご紹介しました。Webアプリケーションの開発環境ではオレオレ証明書を利用しがちですが、これだけ手軽にTLS証明書が利用できるのであれば開発環境でも正規のTLS証明書でデバッグなどに使えるのではないでしょうか。